﻿using System;
using System.IO;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Drawing;
using System.Threading;
using System.Threading.Tasks;
using System.Net;
using System.Net.Http;
using System.Web.Http;
using System.Net.Http.Headers;

using Flagwind.IO;

namespace Flagwind.Storages.Web
{
    public class RemoteFileAccessor
    {
        #region 常量定义

        private const string EXTENDED_PROPERTY_PREFIX = "x-Flagwind-";

        #endregion

        #region 成员字段

        private string _basePath;

        #endregion

        #region 构造方法

        public RemoteFileAccessor()
        {
        }

        public RemoteFileAccessor(string basePath)
        {
            _basePath = basePath;
        }

        #endregion

        #region 公共属性

        public string BasePath
        {
            get
            {
                return _basePath;
            }
            set
            {
                if(string.IsNullOrWhiteSpace(value))
                {
                    _basePath = null;
                }
                else
                {
                    var text = value.Trim();
                    _basePath = text + (text.EndsWith("/") ? string.Empty : "/");
                }
            }
        }

        #endregion

        #region 公共方法

        /// <summary>
        /// 下载指定路径的文件。
        /// </summary>
        /// <param name="path">指定要下载的文件的相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
        public HttpResponseMessage Read(string path)
        {
            if(string.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException(nameof(path));

            var filePath = this.GetFilePath(path);
            var properties = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            if(!FileSystem.File.Exists(filePath))
                throw new HttpResponseException(HttpStatusCode.NotFound);

            var stream = FileSystem.File.Open(filePath, FileMode.Open, FileAccess.Read, properties);
            var mediaType = System.Web.MimeMapping.GetMimeMapping(path);

            return this.ReadStream(stream, mediaType);
        }

		/// <summary>
		/// 下载指定的路径的图片。
		/// </summary>
		/// <param name="path">指定要下载的文件的相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
		/// <param name="width">指定要下载的图片的宽度。</param>
		/// <param name="height">指定要下载的图片的高度。</param>
		/// <param name="thumb">指定要下载的图片是缩放或者裁剪。 0 缩放；1 裁剪(能等比缩放就缩放)；2 强制裁剪 </param>
		/// <param name="site">指定要裁剪的图片从哪里开始裁剪 0中间；1左边；2右边</param>
		/// <returns></returns>
		public HttpResponseMessage ReadImage(string path, int? width = null, int? height = null, int? thumb = null, int? site = null)
		{
			if(string.IsNullOrWhiteSpace(path))
				throw new ArgumentNullException(nameof(path));

			var originalPath = string.Empty;
			var originalFilePath = string.Empty;

			// 如果没有明确指定要缩放的尺寸，则根据图片路径去解析尺寸
			if(width == null && height == null)
			{
				// 根据图片路径解析原始图片路径和尺寸
				originalPath = Utility.EnsureImageSize(path, out width, out height, out thumb, out site);
				originalFilePath = this.GetFilePath(originalPath);

				// 如果没有解析出尺寸来，则表明不需要动态缩放
				if(width == null && height == null)
					return this.Read(path);
			}
			else
			{
				originalPath = path;
				originalFilePath = this.GetFilePath(originalPath);
			}

			// 采用原始文件名作为目录，以后所有缩放的尺寸都放在该目录下面
			var thumbDirectory = Utility.EnsureImageDirectory(originalPath);

			// 如果目录不存在，则新建缩放图目录
			if(!FileSystem.Directory.Exists(thumbDirectory))
				FileSystem.Directory.Create(thumbDirectory);

			thumb = thumb.HasValue ? thumb.Value : 0;
			site = site.HasValue ? site.Value : 0;

			// 根据指定缩放尺寸，解析缩放/裁剪图路径
			var thumbPath = Utility.EnsureImagePath(thumbDirectory, System.IO.Path.GetExtension(path), width.Value, height.Value, thumb.Value, site.Value);

			// 获取指定尺寸缩略/裁剪图文件路径
			var thumbFilePath = this.GetFilePath(thumbPath);

			// 如果指定尺寸缩略/裁剪图存在，则直接读取
			if(FileSystem.File.Exists(thumbFilePath))
				return this.Read(thumbPath);

			// 解析媒体类型
			var mediaType = System.Web.MimeMapping.GetMimeMapping(path);
			var properties = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

			// 从文件系统中读取原图
			var originalStream = FileSystem.File.Open(originalFilePath, FileMode.Open, FileAccess.Read, properties);

			if(originalStream == null)
				throw new HttpResponseException(HttpStatusCode.NotFound);

			// 创建原始图片实例
			var originalImage = Image.FromStream(originalStream);

			// 如果没有缩放（如：尺寸超过原图），则直接返回原图
			if(width >= originalImage.Width && height >= originalImage.Height)
			{
				// 还原流的指针
				originalStream.Position = 0;

				return this.ReadStream(originalStream, mediaType);
			}

			// 开始缩放/裁剪图片
			Image thumbImage = Utility.GetReducedImage(width.Value, height.Value, thumb.Value, site.Value, originalImage);

			try
			{
				using(var thumbStream = new MemoryStream())
				{
					thumbImage.Save(thumbStream, Utility.EnsureImageFormat(mediaType));

					using(var fileStream = FileSystem.File.Open(thumbFilePath, FileMode.Create, FileAccess.Write))
					{
						var bytes = thumbStream.GetBuffer();

						fileStream.Write(bytes, 0, bytes.Length);
					}

					return this.ReadStream(thumbStream, mediaType);
				}
			}
			finally
			{
				originalStream.Dispose();
				originalImage.Dispose();
				thumbImage.Dispose();
			}
		}

		/// <summary>
		/// 获取指定文件的外部访问路径。
		/// </summary>
		/// <param name="path">指定的文件相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
		/// <returns>返回指定文件的外部访问路径。</returns>
		public string GetUrl(string path)
        {
            if(string.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException(nameof(path));

            return FileSystem.GetUrl(this.GetFilePath(path));
        }

        /// <summary>
        /// 获取指定路径的文件描述信息。
        /// </summary>
        /// <param name="path">指定要获取的文件的相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
        /// <returns>返回的指定的文件详细信息。</returns>
        public Task<Flagwind.IO.FileInfo> GetInfo(string path)
        {
            if(string.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException(nameof(path));

            return FileSystem.File.GetInfoAsync(this.GetFilePath(path));
        }

        /// <summary>
        /// 删除指定相对路径的文件。
        /// </summary>
        /// <param name="path">指定要删除的文件的相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
        public async Task<bool> Delete(string path)
        {
            if(string.IsNullOrWhiteSpace(path))
                throw new ArgumentNullException("path");

            if(Utility.IsImagePath(path))
            {
                // 采用原始文件名作为目录，所有缩放的尺寸都放在该目录下面
                var thumbDirectory = Utility.EnsureImageDirectory(path);

                // 删除所有缩略图
                if(FileSystem.Directory.Exists(thumbDirectory))
                    FileSystem.Directory.Delete(thumbDirectory);
            }

            return await FileSystem.File.DeleteAsync(this.GetFilePath(path));
        }

        /// <summary>
        /// 修改指定路径的文件描述信息。
        /// </summary>
        /// <param name="request">网络请求消息。</param>
        /// <param name="path">指定要修改的文件相对路径或绝对路径（绝对路径以/斜杠打头）。</param>
        public async Task<bool> SetInfo(HttpRequestMessage request, string path)
        {
            if(request == null)
            {
                throw new ArgumentNullException("request");
            }

            if(string.IsNullOrWhiteSpace(path))
            {
                throw new ArgumentNullException("path");
            }

            var properties = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);

            foreach(var header in request.Headers)
            {
                if(header.Key.Length > EXTENDED_PROPERTY_PREFIX.Length && header.Key.StartsWith(EXTENDED_PROPERTY_PREFIX, StringComparison.OrdinalIgnoreCase))
                {
                    properties[header.Key.Substring(EXTENDED_PROPERTY_PREFIX.Length)] = string.Join("", header.Value);
                }
            }

            if(request.Content.IsFormData())
            {
                var form = await request.Content.ReadAsFormDataAsync();

                foreach(string fieldName in form)
                {
                    if(fieldName.Length > EXTENDED_PROPERTY_PREFIX.Length && fieldName.StartsWith(EXTENDED_PROPERTY_PREFIX, StringComparison.OrdinalIgnoreCase))
                    {
                        properties[fieldName.Substring(EXTENDED_PROPERTY_PREFIX.Length)] = form[fieldName];
                    }
                }
            }

            if(properties.Count > 0)
            {
                return await FileSystem.File.SetInfoAsync(this.GetFilePath(path), properties);
            }

            return false;
        }

        /// <summary>
        /// 将网络请求中的一个文件或多个文件写入到指定的目录中。
        /// </summary>
        /// <param name="request">网络请求消息。</param>
        /// <param name="directory">指定文件写入的目录路径（绝对路径以“/”斜杠符打头）；如果为空(null)或全空字符串，则写入目录为<see cref="BasePath"/>属性指定的路径。</param>
        /// <param name="onWriting">当文件写入前激发的通知回调。</param>
        /// <returns>返回写入成功的<see cref="Flagwind.IO.FileInfo"/>文件描述信息实体对象集。</returns>
        public async Task<IEnumerable<Flagwind.IO.FileInfo>> Write(HttpRequestMessage request, string directory = null, Action<WritingEventArgs> onWriting = null)
        {
            var directoryPath = this.GetFilePath(directory);

            //检测请求的内容是否为Multipart类型
            if(!request.Content.IsMimeMultipartContent("form-data"))
                throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);

            //创建自定义头的字典
            var headers = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            //构建自定义头的字典内容
            foreach(var header in request.Headers)
            {
                if(header.Key.Length > EXTENDED_PROPERTY_PREFIX.Length && header.Key.StartsWith(EXTENDED_PROPERTY_PREFIX, StringComparison.OrdinalIgnoreCase))
                {
                    headers[header.Key.Substring(EXTENDED_PROPERTY_PREFIX.Length)] = string.Join("", header.Value);
                }
            }

            //创建多段表单信息的文件流操作的供应程序
            var provider = new MultipartStorageFileStreamProvider(directoryPath, headers, onWriting);

            //从当前请求内容读取多段信息并写入文件中
            var result = await request.Content.ReadAsMultipartAsync(provider);

            if(result.FormData != null && result.FormData.Count > 0)
            {
                foreach(var fileEntry in result.FileData)
                {
                    var prefix = EXTENDED_PROPERTY_PREFIX + fileEntry.Key + "-";
                    var updateRequires = false;

                    foreach(var formEntry in result.FormData)
                    {
                        if(formEntry.Key.Length > prefix.Length && formEntry.Key.StartsWith(prefix, StringComparison.OrdinalIgnoreCase))
                        {
                            updateRequires = true;

                            fileEntry.Value.Properties[formEntry.Key.Substring(prefix.Length)] = formEntry.Value;
                        }
                    }

                    if(updateRequires)
                    {
                        await FileSystem.File.SetInfoAsync(fileEntry.Value.Path.Url, fileEntry.Value.Properties);
                    }
                }
            }

            //返回新增的文件信息实体集
            return result.FileData.Values;
        }

        #endregion

        #region 私有方法

        private HttpResponseMessage ReadStream(Stream stream, string mediaType)
        {
            if(stream == null)
                throw new HttpResponseException(HttpStatusCode.NotFound);

            if(string.IsNullOrWhiteSpace(mediaType))
                throw new ArgumentNullException(nameof(mediaType));

            // 创建当前文件的流内容
            var content = new StreamContent(stream);
            var properties = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);

            // 设置返回的内容头信息
            // content.Headers.ContentDisposition = new ContentDispositionHeaderValue("attachment");
            content.Headers.ContentType = new MediaTypeHeaderValue(mediaType);

            if(properties.Count > 0)
            {
                string fileName, lastModified;

                if(properties.TryGetValue("FileName", out fileName))
                {
                    content.Headers.ContentDisposition.FileName = fileName;
                }

                if(properties.TryGetValue("HTTP:LastModified", out lastModified))
                {
                    DateTimeOffset modifiedTime;

                    if(Flagwind.Common.Convert.TryConvertValue<DateTimeOffset>(lastModified, out modifiedTime))
                    {
                        content.Headers.LastModified = modifiedTime;
                    }
                }
            }

            return new HttpResponseMessage(HttpStatusCode.OK)
            {
                Content = content
            };
        }

        private string EnsureBasePath(out string scheme)
        {
            string path;

            if(Flagwind.IO.Path.TryParse(this.BasePath, out scheme, out path))
            {
                return (scheme ?? Flagwind.IO.FileSystem.Scheme) + ":" + (path ?? "/");
            }

            scheme = Flagwind.IO.FileSystem.Scheme;
            return scheme + ":/";
        }

        private string GetFilePath(string path)
        {
            string scheme;
            string basePath = this.EnsureBasePath(out scheme);

            if(string.IsNullOrWhiteSpace(path))
                return basePath;

            // 判断是否携带 Scheme
            if(!string.IsNullOrWhiteSpace(Flagwind.IO.Path.GetScheme(path)))
                return path;

            path = Uri.UnescapeDataString(path).Trim();

            if(path.StartsWith("/"))
            {
                return scheme + ":" + path;
            }
            else
            {
                return Flagwind.IO.Path.Combine(basePath, path);
            }
        }

        #endregion

        #region 嵌套子类

        public class WritingEventArgs : EventArgs
        {
            #region 成员字段

            private int _index;
            private bool _cancel;
            private bool _overwrite;
            private string _directory;
            private string _fileName;

            #endregion

            #region 构造方法

            public WritingEventArgs(string directory, string fileName, int index)
            {
                _index = index;
                _cancel = false;
                _overwrite = false;
                _directory = directory;
                _fileName = fileName;
            }

            #endregion

            #region 公共属性

            /// <summary>
            /// 获取当前文件的序号，从零开始。
            /// </summary>
            public int Index
            {
                get
                {
                    return _index;
                }
            }

            /// <summary>
            /// 获取或设置一个值，指示是否取消后续的文件写入操作。
            /// </summary>
            public bool Cancel
            {
                get
                {
                    return _cancel;
                }
                set
                {
                    _cancel = value;
                }
            }

            /// <summary>
            /// 获取或设置一个值，指示当前文件操作是否为覆盖写入的方式。
            /// </summary>
            public bool Overwrite
            {
                get
                {
                    return _overwrite;
                }
                set
                {
                    _overwrite = value;
                }
            }

            /// <summary>
            /// 获取当前要写入文件所在的目录地址。
            /// </summary>
            public string Directory
            {
                get
                {
                    return _directory;
                }
            }

            /// <summary>
            /// 获取或设置要写入的文件名，如果未包含扩展名则使用上传文件的原始扩展名。
            /// </summary>
            public string FileName
            {
                get
                {
                    return _fileName;
                }
                set
                {
                    _fileName = value;
                }
            }

            #endregion
        }

        private class MultipartStorageFileStreamProvider : MultipartStreamProvider
        {
            #region 成员字段

            private int _index;
            private string _directoryPath;
            private IDictionary<string, string> _headers;
            private IDictionary<string, Flagwind.IO.FileInfo> _fileData;
            private IDictionary<string, string> _formData;
            private Collection<bool> _isFormData;
            private Action<WritingEventArgs> _onWriting;

            #endregion

            #region 构造方法

            public MultipartStorageFileStreamProvider(string directoryPath, IDictionary<string, string> headers, Action<WritingEventArgs> onWriting)
            {
                if(string.IsNullOrWhiteSpace(directoryPath))
                {
                    throw new ArgumentNullException("directoryPath");
                }

                _directoryPath = directoryPath;
                _headers = headers;
                _fileData = new Dictionary<string, Flagwind.IO.FileInfo>();
                _isFormData = new Collection<bool>();
                _formData = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
                _onWriting = onWriting;
            }

            #endregion

            #region 公共属性

            public string DirectoryPath
            {
                get
                {
                    return _directoryPath;
                }
            }

            public IDictionary<string, Flagwind.IO.FileInfo> FileData
            {
                get
                {
                    return _fileData;
                }
            }

            public IDictionary<string, string> FormData
            {
                get
                {
                    return _formData;
                }
            }

            #endregion

            #region 重写方法

            public override Stream GetStream(HttpContent parent, HttpContentHeaders headers)
            {
                if(parent == null)
                    throw new ArgumentNullException(nameof(parent));

                if(headers == null)
                    throw new ArgumentNullException(nameof(headers));

                // 判断当前内容项是否为普通表单域，如果是则返回一个内存流
                if(headers.ContentDisposition == null || string.IsNullOrEmpty(headers.ContentDisposition.FileName))
                {
                    // 在表单数据标记列表中按顺序将当前内容标记为普通表单域
                    _isFormData.Add(true);

                    // 返回一个新的内存流
                    return new MemoryStream();
                }

                //获取当前内容项的内容类型
                var contentType = headers.ContentType == null ? string.Empty : headers.ContentType.MediaType;

                if(string.IsNullOrWhiteSpace(contentType))
                    contentType = System.Web.MimeMapping.GetMimeMapping(headers.ContentDisposition.FileName);

                string fileName = null;
                var dispositionName = UnquoteToken(headers.ContentDisposition.Name);
                var extensionName = System.IO.Path.GetExtension(UnquoteToken(headers.ContentDisposition.FileName));

                if(!string.IsNullOrWhiteSpace(dispositionName))
                {
                    // 获取请求头中显式指定的文件名（注意：该文件名支持模板格式）
                    if(_headers.TryGetValue(dispositionName + ".name", out fileName))
                    {
                        fileName = Flagwind.Text.TemplateEvaluatorManager.Default.Evaluate<string>(fileName, null).ToLowerInvariant() + extensionName;
                    }
                }

                // 定义文件写入的模式
                var overwrite = false;

                // 执行写入前的回调方法
                if(_onWriting != null)
                {
                    // 创建回调参数
                    var args = new WritingEventArgs(_directoryPath, fileName, Interlocked.Increment(ref _index));

                    // 执行写入前的回调
                    _onWriting(args);

                    if(args.Cancel)
                        return null;

                    // 获取写入的文件名和写入模式
                    fileName = args.FileName;
                    overwrite = args.Overwrite;
                }

                // 如果文件名为空，则生成一个以“当前日期-时间-随机数.ext”的默认文件名
                if(string.IsNullOrWhiteSpace(fileName))
                {
                    fileName = string.Format("{0:yyyyMMdd-HHmmss}-{1}{2}", DateTime.Now, (uint)Flagwind.Common.RandomGenerator.GenerateInt32(), extensionName);
                }
                else if(!fileName.EndsWith(extensionName))
                {
                    fileName = fileName + extensionName;
                }

                // 生成文件的完整路径
                var filePath = Flagwind.IO.Path.Combine(_directoryPath, fileName);

                // 生成文件信息的描述实体
                Flagwind.IO.FileInfo fileInfo = new Flagwind.IO.FileInfo(filePath, (headers.ContentDisposition.Size.HasValue ? headers.ContentDisposition.Size.Value : -1), DateTime.Now, null, FileSystem.GetUrl(filePath));

                // 将上传的原始文件名加入到文件描述实体的扩展属性中
                fileInfo.Properties.Add("FileName", Uri.UnescapeDataString(UnquoteToken(headers.ContentDisposition.FileName)));

                if(_headers != null && _headers.Count > 0 && !string.IsNullOrWhiteSpace(dispositionName))
                {
                    // 从全局头里面查找当前上传文件的自定义属性
                    foreach(var header in _headers)
                    {
                        if(header.Key.Length > dispositionName.Length + 1 && header.Key.StartsWith(dispositionName + "-", StringComparison.OrdinalIgnoreCase))
                        {
                            fileInfo.Properties.Add(header.Key.Substring(dispositionName.Length + 1), header.Value);
                        }
                    }
                }

                var infoKey = string.IsNullOrWhiteSpace(dispositionName) ? fileName : dispositionName;

                // 将文件信息对象加入到集合中
                _fileData.Add(infoKey, fileInfo);

                // 在表单数据标记列表中按顺序将当前内容标记为非普通表单域（即二进制文件域）
                _isFormData.Add(false);

                try
                {
                    // 调用文件系统创建目录
                    if(!FileSystem.Directory.Exists(_directoryPath))
                        FileSystem.Directory.Create(_directoryPath);

                    // 调用文件系统根据完整文件路径去创建一个新建文件流
                    return FileSystem.File.Open(filePath, (overwrite ? FileMode.Create : FileMode.CreateNew), FileAccess.Write, (fileInfo.HasProperties ? fileInfo.Properties : null));
                }
                catch
                {
                    if(fileInfo != null)
                    {
                        _fileData.Remove(infoKey);
                    }

                    throw;
                }
            }

            public override async Task ExecutePostProcessingAsync()
            {
                int index = 0;

                foreach(var content in this.Contents)
                {
                    if(_isFormData[index++])
                    {
                        _formData.Add(UnquoteToken(content.Headers.ContentDisposition.Name), await content.ReadAsStringAsync());
                    }
                    else
                    {
                        if(content.Headers.ContentDisposition != null && content.Headers.ContentDisposition.Size.HasValue)
                        {
                            Flagwind.IO.FileInfo info;

                            if(_fileData.TryGetValue(UnquoteToken(content.Headers.ContentDisposition.Name), out info))
                            {
                                info.Size = content.Headers.ContentDisposition.Size.Value;
                            }
                        }
                    }
                }
            }

            #endregion

            #region 私有方法

            private string UnquoteToken(string token)
            {
                if(string.IsNullOrWhiteSpace(token))
                {
                    return string.Empty;
                }

                if(token.Length > 1 && token[0] == '"' && token[token.Length - 1] == '"')
                {
                    return token.Substring(1, token.Length - 2);
                }

                return token;
            }

            #endregion
        }

        #endregion
    }
}